<script lang="ts">
import {bottom, compact, getAllCollisions, getLayoutItem, moveElement, validateLayout} from "../utils/grid";
import {Layout, LayoutItem} from "../utils/types";
import {PropType, reactive, ref} from "vue";
import $bus from "../utils/gridBus";
import eventBus from "./eventBus"


/* 选项 api */
export default {
  name: "GridLayout",
  provide() {
    return {
      layoutObj: this.layoutObj,
    }
  },
  props: {
    // 为true时，容器的高度会根据内容进行伸缩以适应内容的高度。
    // If true, the container height swells and contracts to fit contents
    autoSize: {
      type: Boolean,
      default: true
    },
    // 列数
    colNum: {
      type: Number,
      default: 12
    },
    // 是否可以碰撞
    preventCollision: {
      type: Boolean,
      default: false
    },
    restoreOnDrag: {
      type: Boolean,
      default: false
    },
    // 是否可以拖动
    isDraggable: {
      type: Boolean,
      default: false
    },
    // 行高
    // row height. This is the height in pixels of a row in the grid.
    rowHeight: {
      type: Number,
      default: 150
    },
    maxRows: {
      type: Number,
      default: Infinity
    },
    margin: {
      type: Array as unknown as PropType<[number, number]>,
      default: function () {
        return [10, 10];
      }
    },
    modelValue: {
      type: Array as PropType<Layout>,
      default: () => []
    },
    verticalCompact: {
      type: Boolean,
      default: true
    },
  },
  data: function () {
    // 宽度
    const width = ref(0)
    const gridRef = ref(null);
    const layoutObj = reactive({
      "colNum": this.colNum,
      "rowHeight": this.rowHeight,
      "margin": this.margin,
      "width": 0,
      "isDraggable": this.isDraggable,
      "maxRows": this.maxRows,
    })
    return {
      // 动态样式，只有高度改变
      mergedStyle: {},
      width,
      layoutObj,
      gridRef,
      isDragging: false,
      placeholder: {
        x: 0,
        y: 0,
        w: 0,
        h: 0,
        i: -1
      },
      positionsBeforeDrag: {},
      resizeEventHandler: null,
      dragEventHandler: null,
    }
  },
  created() {
    const self = this;

    self.resizeEventHandler = function (eventType, i, x, y, h, w) {
      self.resizeEvent(eventType, i, x, y, h, w);
    };
    self.dragEventHandler = function (eventType, i, x, y, h, w) {
      self.dragEvent(eventType, i, x, y, h, w);
    };
    eventBus.on("resizeEvent", self.resizeEventHandler)
    eventBus.on("dragEvent", self.dragEventHandler)


    // 对传入的 modelValue 进行校验
    validateLayout(this.modelValue)
    // 对 modelValue 进行修改
    this.$emit("update:modelValue", compact(this.modelValue, true))
    this.updateHeight();


    $bus.on("dragEvent", this.dragEvent)
  },
  mounted() {
    // 监听 resize 事件，执行一遍
    this.handleResize();
    window.addEventListener("resize", this.handleResize);
  },
  beforeUnmount() {
    window.removeEventListener("resize", this.handleResize);
  },

  // 定义函数
  methods: {
    /**
     * 改变大小后触发的函数
     */
    resizeEvent(eventName: string, i: string, x: number, y: number, h: number, w: number) {
    },
    /**
     * 更新高度
     */
    updateHeight: function () {
      this.mergedStyle = {
        height: this.containerHeight() as string,
        backgroundColor: "#C7C7C7"
      };
    },

    /**
     * 获取元素的高度
     */
    containerHeight: function () {
      // 一般是自适应
      if (!this.autoSize) return;
      // console.log("bottom: " + bottom(this.layout))
      // console.log("rowHeight + margins: " + (this.rowHeight + this.margin[1]) + this.margin[1])
      const containerHeight = bottom(this.modelValue) * (this.rowHeight + this.margin[1]) + this.margin[1] + 'px';
      return containerHeight;
    },

    /**
     * 监听窗口大小
     */
    handleResize: function () {
      if (this.$refs !== null && this.$refs.gridRef !== null && this.$refs.gridRef !== undefined) {
        const gridElement = this.$refs.gridRef as HTMLElement;
        const width = gridElement.offsetWidth;
        this.layoutObj.width = width
      }
      this.updateHeight();
    },
    /**
     * 拖动事件
     * @param eventName
     * @param id
     * @param x
     * @param y
     * @param h
     * @param w
     */
    dragEvent: function ({eventName, id, x, y, h, w}: any) {
      console.log("dragEvent 事件:" + eventName + " id=" + id + ", x=" + x + ", y=" + y);
      let l = getLayoutItem(this.modelValue, id);
      console.log("l", l)
      // GetLayoutItem sometimes returns null object
      if (l === undefined || l === null) {
        l = {x: 0, y: 0}
      }
      if (eventName === "dragstart" && !this.verticalCompact) {
        // save positions before drag
        this.positionsBeforeDrag = this.modelValue.reduce((result, {i, x, y}) => ({
          ...result,
          [i]: {x, y}
        }), {});
      }

      if (eventName === "dragmove" || eventName === "dragstart") {
        this.placeholder.i = id;
        this.placeholder.x = l.x;
        this.placeholder.y = l.y;
        this.placeholder.w = w;
        this.placeholder.h = h;
        this.$nextTick(function () {
          this.isDragging = true;
        });
        //this.$broadcast("updateWidth", this.width);
        // this.eventBus.$emit("updateWidth", this.width);
      } else {
        this.$nextTick(function () {
          this.isDragging = false;
        });
      }

      // Move the element to the dragged location.
      let lay = moveElement(this.modelValue, l, x, y, true, this.preventCollision)
      this.$emit("update:modelValue", lay)
      // this.modelValue = moveElement(this.layout, l, x, y, true, this.preventCollision);

      if (this.restoreOnDrag) {
        // Do not compact items more than in layout before drag
        // Set moved item as static to avoid to compact it
        l.static = true;
        this.$emit("update:modelValue", compact(this.modelValue, this.verticalCompact, this.positionsBeforeDrag))
        // compact(this.modelValue, this.verticalCompact, positionsBeforeDrag);
        l.static = false;
        this.$emit("update:modelValue", compact(this.modelValue, this.verticalCompact))
      } else {
        // compact(this.modelValue, this.verticalCompact);
      }

      // needed because vue can't detect changes on array element properties
      // this.eventBus.$emit("compact");
      this.updateHeight();
      if (eventName === 'dragend') {
        // positionsBeforeDrag = null
        delete this.positionsBeforeDrag;
        // this.$emit('layout-updated', this.layout);
      }
    }
  },
  computed: {}
}
</script>


<template>
  <div ref="gridRef" class="vue-grid-layout" :style="mergedStyle">
    <slot></slot>
    <grid-item class="vue-grid-placeholder" v-show="isDragging" :x="placeholder.x" :y="placeholder.y" :w="placeholder.w"
               :h="placeholder.h" :i="placeholder.i"></grid-item>
  </div>
</template>


<style scoped>
.vue-grid-layout {
  position: relative;
  transition: height 200ms ease;
}
</style>